﻿using System;
using System.Data;
using System.Reflection;
using System.Xml.Linq;

using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Pdb;
using Mono.Cecil.Rocks;
using Mono.Collections.Generic;
using Mono.CompilerServices.SymbolWriter;


public class LuaBlock : IILVisitor
{
    private readonly IInstruction[] _instructions;
    private int _index;

    private void AnalyseInstruction(IList<Instruction> instructions)
    {
        _index = 0;

    }
    void IILVisitor.OnInlineArgument(OpCode opcode, ParameterDefinition parameter)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineBranch(OpCode opcode, int offset)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineByte(OpCode opcode, byte value)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineDouble(OpCode opcode, double value)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineField(OpCode opcode, FieldReference field)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineInt32(OpCode opcode, int value)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineInt64(OpCode opcode, long value)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineMethod(OpCode opcode, MethodReference method)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineNone(OpCode opcode)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineSByte(OpCode opcode, sbyte value)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineSignature(OpCode opcode, CallSite callSite)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineSingle(OpCode opcode, float value)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineString(OpCode opcode, string value)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineSwitch(OpCode opcode, int[] offsets)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineType(OpCode opcode, TypeReference type)
    {
        throw new NotImplementedException();
    }

    void IILVisitor.OnInlineVariable(OpCode opcode, VariableDefinition variable)
    {
        throw new NotImplementedException();
    }
}

public class BlockAnalysis
{
    private readonly MethodContext _methodContext;
    private readonly LuaTypeContext _typeContext;
    public BlockAstTree BlockAstTree { get; }
    public BlockAnalysis(LuaTypeContext context, MethodContext methodContext = null)
    {
        _typeContext = context;
        _methodContext = methodContext ?? new MethodContext();
    }
    private static ICodeWriter _debug = new LuaCodeConsole();
    static BlockAnalysis()
    {
        _debug = new LuaCodeConsole();
        _debug.Indent += 2;
    }
    public BlockAstTree Analysis(IList<Instruction> instructions)
    {
        _debug.Indent++;
        var blockAstTree = new BlockAstTree();
        Analysis(instructions.First(), null, blockAstTree.Statements);
        _debug.Indent--;
        return blockAstTree;
    }
    LuaAstTree LdC(long value)
    {
        return new LdIntAstTree(value);
    }
    LuaAstTree LdC(double value)
    {
        return new LdRealAstTree(value);
    }
    LuaAstTree LdC(string value)
    {
        return new LdStrAstTree(value);
    }
    LuaAstTree LdToken(object operand)
    {
        switch (operand)
        {
            case TypeReference type:
                return new LdTypeTree(_typeContext.GetTypeReference(type));
            default:
                throw new UnauthorizedAccessException();
        }
    }
    LuaAstTree LdVar(ParameterReference parameter)
    {
        if (parameter.Index < 0)
            return LdVar(_typeContext.CurMethod.SelfArg);
        return LdVar(_typeContext.CurMethod.Args[parameter.Index]);
    }
    LuaAstTree LdVar(VariableReference variable)
    {
        return LdVar(_typeContext.CurMethod.Locals[variable.Index]);
    }
    LuaAstTree LdVar(FieldReference field)
    {
        LuaAstTree tree = new FieldAstTree(_methodContext.Pop(), _typeContext.GetFieldReference(field));
        return new LdVarAstTree(tree);
    }
    LuaAstTree LdSVar(FieldReference field)
    {
        LuaAstTree tree = new FieldAstTree(_typeContext.GetFieldReference(field));
        return new LdVarAstTree(tree);
    }
    LuaAstTree LdVar(LuaVarReference var)
    {
        return new LdVarAstTree(new VarAstTree(var));
    }
    LuaAstTree LdLen()
    {
        return new LdLenAstTree(_methodContext.Pop());
    }
    LuaAstTree LdNull()
    {
        return new LdNullAstTree();
    }
    LuaAstTree LdElemRef()
    {
        var values = _methodContext.Pop(2);
        return new LdElemRefAstTree(values[0], values[1]);
    }
    LuaAstTree StElemRef()
    {
        var values = _methodContext.Pop(3);
        return new StElemRefAstTree(values[0], values[1], values[2]);
    }
    LuaAstTree StVar(FieldReference field)
    {
        LuaAstTree value = _methodContext.Pop();
        LuaAstTree self = new FieldAstTree(_methodContext.Pop(), _typeContext.GetFieldReference(field));
        return new StVarAstTree(self, value);
    }
    LuaAstTree StVar(VariableReference variable)
    {
        return new StVarAstTree(new VarAstTree(_typeContext.CurMethod.Locals[variable.Index]), _methodContext.Pop());
    }
    LuaAstTree StindRef()
    {
        var values = _methodContext.Pop(2);
        return new StVarAstTree(values[0], values[1]);
    }
    bool Call(MethodReference method, out LuaAstTree tree)
    {
        var parameters = _methodContext.Pop(method.Parameters.Count);
        var luaType = _typeContext.GetTypeReference(method.DeclaringType);
        var luaMethod = _typeContext.GetMethodReference(luaType, method);
        if (method.Name == ".ctor")
        {
            var var = _methodContext.Pop();
            foreach (var arg in parameters)
                _methodContext.Push(arg);
            tree = new StVarAstTree(var, NewObj(method));
            return true;
        }
        if (luaMethod.HasSelf)
            tree = new SelfCallAstTree(_methodContext.Pop(), luaMethod, parameters);
        else
            tree = new StaticCallAstNode(luaType, luaMethod, parameters);
        if (luaMethod.ReturnType != ObjectKind.Void)
        {
            tree.ResultType = luaMethod.ReturnType;
            _methodContext.Push(tree);
            return false;
        }
        return true;
    }
    LuaAstTree NewObj(MethodReference method)
    {
        var parameters = _methodContext.Pop(method.Parameters.Count);
        var luaType = _typeContext.GetTypeReference(method.DeclaringType);
        var ctor = _typeContext.GetMethodReference(luaType, method);
        return new NewObjAstTree(luaType, ctor, parameters);
    }
    LuaAstTree NewArr(TypeReference type)
    {
        var luaType = _typeContext.GetTypeReference(type);
        return new NewArrTree(luaType);
    }
    LuaAstTree Br(Instruction target)
    {
        return new BrAstTree($"IL_{target.Offset:X}");
    }
    LuaAstTree Beq(ref Instruction inst, Instruction next, IList<LuaAstTree> trees)
    {
        var values = _methodContext.Pop(2);
        _methodContext.Push(new OpAstTree("==", values[0], values[1]));
        return BrFalse(ref inst, next, trees);
    }
    LuaAstTree Ret()
    {
        List<LuaAstTree> results = new List<LuaAstTree>();
        foreach (var parameter in _typeContext.CurMethod.Args)
            if (parameter.IsReturn)
                results.Add(new VarAstTree(parameter));
        if (_typeContext.CurMethod.ReturnType != ObjectKind.Void)
        {
            var result = _methodContext.Pop();
            if (_typeContext.CurMethod.ReturnType == ObjectKind.Boolean && result.ResultType == ObjectKind.Integer)
                result = new LdBoolAstTree((int)(result.GetResult<long>()));
            results.Add(result);
        }
        return new RetAstTree(results.ToArray());
    }
    LuaAstTree Mul()
    {
        var values = _methodContext.Pop(2);
        return new OpAstTree("*", values[0], values[1]);
    }
    LuaAstTree Add()
    {
        var values = _methodContext.Pop(2);
        return new OpAstTree("+", values[0], values[1]);
    }
    LuaAstTree IsInst(TypeReference type)
    {
        var luaType = _typeContext.GetTypeReference(type);
        return new IsAstTree(_methodContext.Pop(), luaType);
    }
    LuaAstTree Ceq()
    {
        var values = _methodContext.Pop(2);
        if (values[0].ResultType != values[1].ResultType && (values[0].ResultType == ObjectKind.Boolean || values[1].ResultType == ObjectKind.Boolean))
            if (values[1].ResultType == ObjectKind.Integer)
                values[1] = new LdBoolAstTree((int)(values[1].GetResult<long>()));
            else
                values[0] = new LdBoolAstTree((int)(values[1].GetResult<long>()));
        return new OpAstTree("==", values[0], values[1]);
    }
    private bool Dup(out LuaAstTree tree)
    {
        var raw = _methodContext.Pop();
        if (raw is DupAstTree dup)
        {
            tree = null;
            _methodContext.Push(dup, dup);
            return false;
        }
        dup = new DupAstTree(_tempVarCount++, raw);
        tree = dup;
        _methodContext.Push(dup, dup);
        return true;
    }
    LuaAstTree BrTrue(ref Instruction inst, Instruction target, IList<LuaAstTree> trees)
    {
        throw new NotImplementedException();
    }
    LuaAstTree BrFalse(ref Instruction inst, Instruction target, IList<LuaAstTree> trees)
    {
        var nextInst = inst.Next;
        var expr = _methodContext.Pop();
        if (expr is LdVarAstTree lvat
            && trees.Count > 0
            && trees[trees.Count - 1] is StVarAstTree svat)
        {
            trees.RemoveAt(trees.Count - 1);
            expr = svat.Value;
        }
        var thenBlockTree = new BlockAstTree();
        BlockAstTree elseBlockTree = null;
        Analysis(nextInst, target, thenBlockTree.Statements);
        if (target.Previous.OpCode.Code == Code.Br)
        {
            inst = (Instruction)target.Previous.Operand;
            elseBlockTree = new BlockAstTree();
            Analysis(target, inst, elseBlockTree.Statements);
        }
        else
        {
            inst = target;
        }
        return new IfAstTree(expr, thenBlockTree, elseBlockTree);
    }
    void Analysis(Instruction first, Instruction last, IList<LuaAstTree> trees)
    {
        _debug.Indent++;
    begin:
        while (first != last)
        {
            var opcode = first.OpCode;
            var operand = first.Operand;
            _debug.Comment(first.ToString());
            switch (opcode.Code)
            {
                case Code.Ldarga:
                case Code.Ldarg:
                    _methodContext.Push(LdVar((ParameterReference)operand));
                    break;
                case Code.Ldloca:
                case Code.Ldloc:
                    _methodContext.Push(LdVar((VariableReference)operand));
                    break;
                case Code.Ldfld:
                case Code.Ldflda:
                    _methodContext.Push(LdVar((FieldReference)operand));
                    break;
                case Code.Ldsfld:
                    _methodContext.Push(LdSVar((FieldReference)operand));
                    break;
                case Code.Ldc_I4:
                    _methodContext.Push(LdC((int)operand));
                    break;
                case Code.Ldc_I8:
                    _methodContext.Push(LdC((long)operand));
                    break;
                case Code.Ldc_R4:
                    _methodContext.Push(LdC((float)operand));
                    break;
                case Code.Ldc_R8:
                    _methodContext.Push(LdC((double)operand));
                    break;
                case Code.Ldstr:
                    _methodContext.Push(LdC((string)operand));
                    break;
                case Code.Ldlen:
                    _methodContext.Push(LdLen());
                    break;
                case Code.Ldnull:
                    _methodContext.Push(LdNull());
                    break;
                case Code.Ldelem_Ref:
                    _methodContext.Push(LdElemRef());
                    break;
                case Code.Stloc:
                    trees.Add(StVar((VariableReference)operand));
                    break;
                case Code.Stfld:
                    trees.Add(StVar((FieldReference)operand));
                    break;
                case Code.Stelem_Ref:
                    trees.Add(StElemRef());
                    break;
                case Code.Callvirt:
                case Code.Call:
                    if (Call((MethodReference)operand, out var tree))
                        trees.Add(tree);
                    break;
                case Code.Newobj:
                    _methodContext.Push(NewObj((MethodReference)operand));
                    break;
                case Code.Newarr:
                    _methodContext.Push(NewArr((TypeReference)operand));
                    break;
                case Code.Br:
                    if (last == first.Next)
                    {
                        first = first.Next;
                        goto end;
                    }
                    if (first.Next == (Instruction)operand)
                        break;
                    Analysis(first.Next, (Instruction)operand, trees);
                    first = (Instruction)operand;
                    break;
                //first = (Instruction)operand;
                //goto begin;
                //break;
                case Code.Ret:
                    trees.Add(Ret());
                    break;
                case Code.Pop:
                    trees.Add(_methodContext.Pop());
                    break;
                case Code.Ceq:
                    _methodContext.Push(Ceq());
                    break;
                case Code.Mul:
                    _methodContext.Push(Mul());
                    break;
                case Code.Add:
                    _methodContext.Push(Add());
                    break;
                case Code.Isinst:
                    _methodContext.Push(IsInst((TypeReference)operand));
                    break;
                case Code.Cgt_Un:
                    _methodContext.Push(new OpAstTree("!=", _methodContext.Pop(), _methodContext.Pop()));
                    break;
                case Code.Clt:
                    _methodContext.Push(new OpAstTree("<", _methodContext.Pop(), _methodContext.Pop()));
                    break;
                case Code.Brfalse:
                    trees.Add(BrFalse(ref first, (Instruction)operand, trees));
                    goto begin;
                case Code.Brtrue:
                    trees.Add(BrTrue(ref first, (Instruction)operand, trees));
                    break;
                case Code.Beq:
                    trees.Add(Beq(ref first, (Instruction)operand, trees));
                    goto begin;
                case Code.Ldtoken:
                    _methodContext.Push(LdToken(operand));
                    break;
                case Code.Stind_Ref:
                case Code.Stind_I4:
                    trees.Add(StindRef());
                    break;
                case Code.Constrained:
                case Code.Nop:
                case Code.Unbox_Any:
                case Code.Unbox:
                case Code.Conv_I4:
                case Code.Box:
                    break;
                case Code.Dup:
                    if (Dup(out var t))
                        trees.Add(t);
                    break;
                case Code.Throw:
                    trees.Add(new ThrowAstTree(_methodContext.Pop()));
                    break;
                default:
                    throw new Exception(first.ToString());
            }
            first = first.Next;
        }
    end:
        _debug.Indent--;

    }
    private int _tempVarCount = 0;
}

